package com.meevle.midiproxy;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;

import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Transmitter;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;


public class MeevleMidiProxy 
	implements Observer  
	{

	private static final int SERVER_PORT = 10000;
	private boolean isServerRunning;
	private JFrame frame; // Main UI window
	private JLabel serverPortLabel;
	private JButton serverStartButton;
	private JButton midiConnectButton;
	private JComboBox midiDeviceListBox;
	private JLabel midiLabel;
	private ArrayList<String> midiDevices;
	private javax.sound.midi.MidiDevice.Info inputInfo[];
	private com.meevle.midiproxy.TcpServer server;
	private MidiDevice inputPort;
	private Transmitter inputTransmitter;
	private MonitoringReceiver inputReceiver;
	private MainJFrameListener closeListener;
	
	public static void main(String[] args)
	{
		MeevleMidiProxy app = new MeevleMidiProxy();
		app.go();
	}
	
	public void go()
	{
		isServerRunning = false;

        javax.sound.midi.MidiDevice.Info info[] = MidiSystem.getMidiDeviceInfo();
        inputInfo = filterInputDevices(info, true);
        midiDevices = new ArrayList<String>();
        for(int i = 0; i < inputInfo.length; i++) {
        	// System.out.print(inputInfo[i].getName());
        	midiDevices.add(inputInfo[i].getName());
        }
        closeListener = new MainJFrameListener();
        
		frame = new JFrame();
		frame.setTitle("Meevle MIDI Proxy");
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.addWindowListener(closeListener);
		
		JPanel displayPanel = new JPanel();
		
		JPanel serverPortLabelPanel = new JPanel();
		serverPortLabelPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		serverPortLabel = new JLabel("Server Port: " + MeevleMidiProxy.SERVER_PORT);
		serverPortLabelPanel.add(serverPortLabel);

		JPanel serverPortInputPanel = new JPanel();
		serverPortInputPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		
		serverStartButton = new JButton("Start Server");
		serverStartButton.addActionListener(new ServerStartListener());
		
		serverPortInputPanel.add(serverStartButton);
		
		JPanel midiLabelPanel = new JPanel();
		midiLabelPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		midiLabel = new JLabel("Choose MIDI Device:");
		midiLabelPanel.add(midiLabel);
		
	    JPanel midiSelectPanel = new JPanel();
		midiSelectPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
		
		// Grab the midi input device list and add it to the combo box
		String[] midiDeviceList = new String[midiDevices.size()];
		Iterator<String> midiIterator = midiDevices.iterator();
		int count = 0;
		while(midiIterator.hasNext()){
			midiDeviceList[count] = midiIterator.next();
			count++;
		}
		
	    midiDeviceListBox = new JComboBox(midiDeviceList);
	    midiDeviceListBox.addActionListener(new MidiComboListener());
		midiConnectButton = new JButton("Start MIDI Connection");
		midiConnectButton.addActionListener(new MidiConnectListener());
		midiConnectButton.setEnabled(false); // Don't want this active until the server has started
		
		midiSelectPanel.add(midiDeviceListBox);
		midiSelectPanel.add(midiConnectButton);
				
		displayPanel.add(serverPortLabelPanel);
		displayPanel.add(serverPortInputPanel);
		displayPanel.add(midiLabelPanel);
		displayPanel.add(midiSelectPanel);
		
		frame.getContentPane().add(BorderLayout.CENTER, displayPanel);
		frame.setSize(450, 175);
		frame.setVisible(true);
	}
	
	private void connectToSelectedDevice() 
	{
		// Determine the device chosen.
		int selectedIndex = midiDeviceListBox.getSelectedIndex();
		javax.sound.midi.MidiDevice.Info inputDeviceInfo = inputInfo[selectedIndex];
		try {
			inputPort = MidiSystem.getMidiDevice(inputDeviceInfo);
			inputPort.open();
			inputTransmitter = inputPort.getTransmitter();
			inputReceiver = new MonitoringReceiver();
			inputReceiver.addObserver(this);
			inputTransmitter.setReceiver(inputReceiver);
		} catch (MidiUnavailableException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		midiDeviceListBox.setEnabled(false);
		// Disable the connect to midi button
		midiConnectButton.setEnabled(false);
		midiConnectButton.setText("MIDI Connected...");
	}

	private void startServer(int portNum) 
	{
		server = new TcpServer(portNum);
		server.start();
		isServerRunning = true;
	}

	public void update(Observable o, Object arg) 
	{
		if(isServerRunning) {
			MidiData d = (MidiData) arg;
			
			switch(d.getType()) {
			case MidiData.TYPE_NOTE_ON:
				server.broadcastMessage("<n1 p=\"" + String.valueOf(d.getId()) + "\" " +
						"v=\"" + String.valueOf(d.getValue()) + "\" " +
						"/>");
				break;
				
			case MidiData.TYPE_NOTE_OFF:
				server.broadcastMessage("<n0 p=\"" + String.valueOf(d.getId()) + "\" />");
				break;
				
			case MidiData.TYPE_CONTROLLER:
				server.broadcastMessage("<c n=\"" + String.valueOf(d.getId()) + "\" " +
										"d=\""+String.valueOf(d.getValue())+"\" " +
										"/>");
				break;			
			}
		}
	}
	
	/**
	 * Loops through all MidiDevice.Info instances and determines which ones are capable of transmitting data.
	 * 
	 * @param infoList Array of MidiDevice.Info instances
	 * @param select Boolean representing whether we want to include or exclude transmitting-capable devices
	 * @return an Array of MidiDevice.Info instances that are capable of transmitting data
	 */
    public static javax.sound.midi.MidiDevice.Info[] filterInputDevices(javax.sound.midi.MidiDevice.Info infoList[], boolean select)
    {
        boolean copyElement[] = new boolean[infoList.length];
        int deviceCount = 0;
        for(int index = 0; index < infoList.length; index++)
            try
            {
                MidiDevice device = MidiSystem.getMidiDevice(infoList[index]);
                boolean hasTransmitter = device.getMaxTransmitters() != 0;
                if(hasTransmitter == select)
                {
                    copyElement[index] = true;
                    deviceCount++;
                }
            }
            catch(MidiUnavailableException e) { }

        javax.sound.midi.MidiDevice.Info outList[] = new javax.sound.midi.MidiDevice.Info[deviceCount];
        int outIndex = 0;
        for(int index = 0; index < infoList.length; index++)
            if(copyElement[index])
            {
                outList[outIndex] = infoList[index];
                outIndex++;
            }

        return outList;
    }
	
	/**
	 * Inner class for receiving the SERVER START button events
	 */
	public class ServerStartListener implements ActionListener 
	{
			
		public void actionPerformed(ActionEvent event)
		{
			serverStartButton.setEnabled(false);
			serverStartButton.setText("Connected...");
			midiConnectButton.setEnabled(true);
			startServer(MeevleMidiProxy.SERVER_PORT);
		}
		
	}
	
	/**
	 * Inner class for receiving the MDII CONNECT button events
	 */
	public class MidiConnectListener implements ActionListener 
	{
			
		public void actionPerformed(ActionEvent event)
		{
			connectToSelectedDevice();
		}
		
	}
	
	/**
	 * Inner class for receiving the MDII CONNECT button events
	 */
	public class MidiComboListener implements ActionListener 
	{
			
		public void actionPerformed(ActionEvent event)
		{
			
		}
		
	}
	
	public class MainJFrameListener implements WindowListener
	{
		public void windowActivated(WindowEvent e) { }

		public void windowClosed(WindowEvent e) {			
			if(server != null) {
				if(server.isAlive()) {
					server.killServer();
					server = null;
				}
			}

			if(inputPort != null) {
				if(inputPort.isOpen()) {
					inputPort.close();
					inputPort = null;
				}
			}
			if(inputReceiver != null) {
				inputReceiver.close();
				inputReceiver = null;
			}
			if(inputTransmitter != null) {
				inputTransmitter.close();
				inputTransmitter = null;
			}
			System.exit(0);
			
		}

		public void windowClosing(WindowEvent e) { }

		public void windowDeactivated(WindowEvent e) { }

		public void windowDeiconified(WindowEvent e) { }

		public void windowIconified(WindowEvent e) { }

		public void windowOpened(WindowEvent e) { }
		
	}
}
